Generics Programming

 

Introduction

JDK 5 has introduced a new syntactical element called generics. Today, generics are an integral part of Java programming, and a solid understanding of this important feature is required. It is examined here in detail.

 

Why Generics?

Through the use of generics, it is possible to create classes, interfaces, and methods that will work in a type-safe manner with various kinds of data. Many algorithms are logically the same no matter what type of data they are being applied to. For example, the mechanism that supports a stack is the same whether that stack is storing items of type Integer, String, Object, or Thread. With generics, you can define an algorithm once, independently of any specific type of data, and then apply that algorithm to a wide variety of data types without any additional effort. The expressive power generics added to the language fundamentally changed the way that Java code is written.

 

Generics versus Collection Framework

The Collections Framework is part of the Java API and is described in detail in in the next module, but a brief mention is useful now. A collection is a group of objects. The Collections framework defines several classes, such as List, Array, Map, etc. that manage collections. The collection classes have always been able to work with any type of object. The benefit that generics added is that the collection classes can now be used with complete type safety. Thus, in addition to being a powerful language element on its own, generics also enabled an existing feature to be substantially improved. This is another reason why generics were such an important addition to Java.

 

Chapter plan

This chapter describes the syntax, theory, and use of generics. It also shows how generics provide type safety for some previously difficult cases. This will follows the discussion of the Collections Framework. There you will find many examples of generics at work.

 

Generic methods

A method in a class that can take any parameter is called a generic method. Suppose, in a class we want to define a method which can take any argument to display its content.

 

Example 2.1.

// A simple Java program to show working of a generic method.

  

class GenericMethod  {

    // Defining a generic method to print any data type

     void genericPrint (T t)  {

          System.out.println (t);

    }

  

    public static void main(String[] args)    {

          GenericMethod aObj;  // Creating an object of the class  GenericMethod

         // Calling generic method with Integer argument

                      aObj.genericDisplay(101);

  

        // Calling generic method with String argument

        aObj.genericDisplay("Joy with Java");

  

        // Calling generic method with double argument

        aObj.genericDisplay(3.1412343);

    }

}

 

Note:

·         You can readily understand the similarity between method overriding and generic method. Both the concepts have the same objective, but in their own ways. The main difference is that in case of method overriding, we have to build code for each overridden method, whereas, with generic method, same code can work for the different types of data.

·         Further, with generic method, theoretically you can pass any type of data as argument; however, with method overriding only a limited number of arguments are allowed.

·         According to the class encapsulation, method overloading and method overriding are also applicable to generic methods.

 

Example 2.2.

The following program to define a static generic method in a class.

 

// A simple Java program to show working of a generic method.

class GenericStaticMethod  {

    // Defining a static generic method to print any data type

     Static <T> void genericPrint (T t)  {

         // The following statement print any type specified by the parameter T

          System.out.println (t.getClass().getName() “:” + t);

    }

  

    public static void main(String[] args)    {

          // GenericMethod  aObj;      // Creating an object of the class  GenericMethod

         // Calling generic method with Integer argument

        genericDisplay(101);

  

        // Calling generic method with String argument

        genericDisplay("Joy with Java");

  

        // Calling generic method with double argument

        genericDisplay(3.1412343);

    }

}

 

Methods with variable list of arguments

If you are a C programmer, then you have noticed that in C, there are some standard methods like printf(…), scanf(…), which takes variable number of arguments. Further, in a C program, you can define a method with variable number of arguments to be passed. It is intersecting to know if you can do the similar things or not in Java!

Obviously, one solution you may think is to write as many as overloading methods. Indeed, this is a naive idea. Fortunately, there are some elegant ideas Java developer provides you. With their idea, you can write a method so that it will accept an arbitrary number of arguments when it is called. This feature in Java is called varargs method also called variable‐arity method. There are three ways:

 

1.  Using an array

2.  Using ellipsis (three dots)

3.  Using Object class

 

Let us examine each idea in the following discussions.

 

Varargs method using array

This is the simplest method. Here, you can define a varargs method with an argument an array (of any type). In other words, the values which you want to pass to a method, store them in an array and then pass the array to the method. That’s all! This approach is illustrated in Program 2.3.

 

Example 2.3.

/* This program demonstrates a varargs method using an array to pass a variable number of arguments to a method.  */

class VarargsMethod1 {

     static void varargsMethod1(int v[])  {

         System.out.print("Number of args: " + v.length +" Elements: ");

         for(int x : v)

             System.out.print(x + " ");

         System.out.println();

    }

    public static void main(String args[])  {

        // Following arrays are created for test...

           int x[] = { 1, 3, 5, 7 };

           int y[] = { 2, 4};

           int z[] = { };

           varargsMethod1 (x); // Passed 4 values to the method

           varargsMethod1 (y); // Passed 4 values to the method

           varargsMethod1 (y); // Passed no argument to the method

    }

}

 

Varargs method using ellipsis (three dots)

The second method is similar to the method but with a different syntax, which is special in Java. The syntax to define varargs method with this approach is given below.

 

      <AccessSpec><ReturnType><MethodName>(<Type>...<arrayName>)          {

       . . . // Method body

     }

 

Example 2.4.

/* This program demonstrates varargs method with ellipses. */

 

class VarargsMethod2 {

    //Defining a varargs method

    static void varargsMethod2(int ...v) {

       System.out.println("Number of arguments: " + v.length);

        for (int i: v) // For each item i in array v

            System.out.print(i + " ");

        System.out.println();

    }

      public static void main(String args[])     {

        // Calling the varargs method with variable arguments

        varargsMethod2 (9);              // One parameter

        varargsMethod2 (1, -2, 3, -4);  // Four parameters

        varargsMethod2 ();             // no parameter

    }

}

.

·         The two approaches need values to be packed into an array prior to calling.

·         Command line arguments is also varargs method for the main() in your Java program.

·         As it is an array to be passed, hence all values that can be passed is of same type.

·         A method can have “normal” parameters along with a variable‐length parameter. However, the variable‐length parameter must be the last parameter declared by the method. For example, the following is a valid varargs method declaration.

.

int varMethod(int a, int b, double c, float ... v) { }

·         In this case, the first three arguments used in a call to varMethod ( ) are matched to the first three parameters. Then, any remaining arguments are assumed to belong to v.

·         You can overload a method that takes a variable‐length argument. Program 4.36 demonstrates how a varrags method can be overloaded.

 

Example 2.5.

/* This program demonstrates the overloading of a varargs method. */

 

class OverloadingVarargs {

      static void varTest(int ... v) {  //Overloading method 1

           System.out.print("Arg(int ...): " + "Number of args: " +

                                              v.length + " Contents: ");

           for(int x : v)

               System.out.print(x + " ");

           System.out.println();

       }

      static void varTest(boolean ... v) {   //Overloading method 2

           System.out.print("Arg(boolean ...) " + "Number of args: " +

                                              v.length + " Contents: ");

           for(boolean x : v)

               System.out.print(x + " ");

           System.out.println();

      }

       static void varTest(String msg, int ... v) { //Overloading method 3

           System.out.print("Arg(String, int ...): " + msg + v.length +

                                                       " Contents: ");

           for(int x : v)

               System.out.print(x + " ");

           System.out.println();

       }

      

      public static void main(String args[])  {

           varTest(1, 2, 3);

           varTest("Testing: ", 10, 20);

           varTest(true, false, false);

      }

}

 

·         The varargs parameter must be last. For example, the following declaration is incorrect:

int varMethod(int a, float ... v, double c) {  }

·               There is one more restriction to be aware of: there must be only one varargs parameter. For example, the following declaration is also invalid.

 

     int varMethod (int ...a, double ...b) { }

·               The attempt to declare the second varargs parameter is illegal.

·               Sometime unexpected errors can result when overloading a method that takes a variable length argument. These errors involve ambiguity because it is possible to create an ambiguous call to an overloaded varargs method. For example, if you exclude overloaded method 3 and change the main() method as follows, then it will give the compilation error.

 

public static void main(String args[]) { varTest(1, 2, 3);  // Okay

     varTest();        // Compile time error

                    // as it does not match with anyone

     varTest(true, false, false); // Okay

}

 

Varargs method using Object class

Possible this is the most elegant approach to implement the varargs method in your Java program. It used the ellipsis and in addition to this, it uses the Object type. For example, to define a varargs method your method declaration may take the following form.

 

public static void methodName(Object...obj) {

/   / Body of the method

}

Further, you may note the restriction that the method can have zero or more parameters preceding this, but this must be the last for reasons you have already learned. Let’s consider Program 5 to demonstrate the mechanism. This program defines a varargs method which takes any type of values with any count.

 

Example 2.6.

/* This program demonstrates the varargs method taking any type of arguments as well as any number. */

 

class VarargsMethod3  {

       public static void varargsMethod3(Object ... obj) {

          for(Object o : obj)

             System.out.print(“ “+o);

          System.out.println();

      }

      public static void main(String[] args) {

           varargsMethod3( 1, “String”, 2.3, true); // Four arguments

           varargsMethod3 ();                       // No arguments

           varargsMethod3 (15, 25, 35, 45, 55);     // Five arguments

     }

}

 

 

Generic class

Let us consider the case of an array type of numbers to be processed in a Java program. Let us consider the case of initializing an array of integers and two methods of printing the values and reversing the array elements. Here, is the program which you should consider to define such a class.

 

class SpecificArrayInt {

// Declaring an array of integer values

// Constructor to load the array. 

// Method to print the array elements

// Method to reverse the array elements

}

 

class MainClassInt {

      //This class utilize the class SpecificArrayInt to manipulate some integer data

}

 

Example 2.7:

// Writing the full code here...

 

class SpecificArrayInt {

      // Declaring an array of integer values

int a;

 

// Constructor to load the array

SpecificArrayInt(int a[]) {

    this.a = a;

} 

 

// Method to print the array elements

void printInt() {

   for(int x : a)

      System.out.println(x);

}

 

// Method to reverse the array elements

void reverseInt() {

    j = a.length;

    for (int i=0; i<j; i++)

        int temp;

        temp = a[i];

a[i] = a[j];

a[j] = temp;

j--;

         }

      }

}

 

class MainClassInt {

      //This class use the class SpecificArrayInt to manipulate data in it

      SpecificArrayInt a = {1, 2, 3, 4, 5};

      a.printInt();

      a.reverseInt();

      a.print();  

}

 

 

Now, suppose, we want to manipulate double data. So, we have to redefine our programs one again. This is shown below.

 

class SpecificArrayDouble {

//Declaring an array of double values

// Constructor to load the array.

// Method to print the array elements

//Method to reverse the array elements

// Swap two elements in the array. It is static class, say

}

 

class MainClassDouble {

        //This class utilize the class SpecificArrayInt to manipulate some integer data

}

 

Example 2.8:

// Writing the full code here...

 

class SpecificArrayDouble {

      // Declaring an array of integer values

double x;

 

// Constructor to load the array

SpecificArrayDouble(double x[]) {

    this.x = x;

} 

 

// Method to print the array elements

void printDouble() {

   for(int y : x)

      System.out.println(y);

}

 

// Method to reverse the array elements

void reverseDouble() {

    j = x.length;

    for (int i=0; i<j; i++)

        double temp;

        temp = a[i];

  a[i] = a[j];

  a[j] = temp;

  j--;

          }

      }

}

 

class MainClassDouble {

      //This class use the class SpecificArrayInt to manipulate data in it

      SpecificArrayDouble x = {1.2, 2.3, 3.4, 4.5, 5.6};

      x.printDouble();

      x.reverseDouble();

      x.print();  

}

 

 

What we should do, if we want to manipulate some names, instead of integer/ double values? We can write another program, which is basically close replica of the first two.

 

class SpecificArrayString {

//Declaring an array of String objects

// Constructor to load the array. 

// A generic method to print the array elements

//A generic method to reverse the array elements

}

Class MainClassString {

 

//This class utilize the class SpecificArrayInt to manipulate some integer data

 

}

 

 

Example 2.9:

// Writing the full code here...

 

class SpecificArrayString {

      // Declaring an array of integer values

String s;

 

// Constructor to load the array

SpecificArrayString(String s[]) {

    this.s = s;

} 

 

// Method to print the array elements

void printString() {

   for(String z : s)

      System.out.println(z);

}

 

// Method to reverse the array elements

void reverseString() {

    j = s.length;

    for (int i=0; i<j; i++)

        String temp;

        temp = a[i];

  a[i] = a[j];

  a[j] = temp;

  j--;

          }

      }

}

class MainClassString {

      //This class use the class SpecificArrayInt to manipulate data in it

      SpecificArrayString s = {“A”, “B”, “C”, “D”, “E”};

      s.printString();

      s.reverseString();

      s.print();  

}

 

Solving the problems with generic class

A class that can refer to any type is known as a generic class. In other words, a generic class, is a class whose methods in it can deal with any type of object in it. Like C++, in Java, you should use < > to specify parameter types in generic class creation.    The syntax for declaring a generic class is as follows:

 

[Access] class <ClassName> <<Type1> [, <Type2>, …]> {

body of the class

}

 

Here is the full syntax for declaring a reference to a generic class and instance creation:

 

<className><typeList> varName = new <className><typeList> (<InputArray>);

 

Example 2.10 gives a simple example to define a generic class with one type and its use in main class.

 

Example 2.10.

/* A Simple Java program to show working of user defined generic class. */

class GenericClass<Type>  {      // Use < > to specify class type

    Type obj;                    // An object of type T is declared

    GenericClass(Type obj) {     // Constructor of the generic class

        this.obj = obj; 

   

    public Type getObject()  {   // A Method in the class

        return this.obj;

    }

}

 

class GenericClassTest  {     // Driver class to test the above

    public static void main (String[] args) {

        GenericClass <Integer> iObj = new GenericClass <Integer>(15);

                              // A class with Integer type

        System.out.println(iObj.getObject());

   

        GenericClass <String> sObj = new GenericClass <String>("Java");

                             // Another class with String type

        System.out.println(sObj.getObject());

    }

}

 

Example 2.11:

In the above discussions, we have shared our programing experience, when we want to process different type of data but using same logic or same application. The generic feature in Java is a right solution to get rid-off code duplicities. Example 8 is a solution with generic facility in Java.

 

class GenericArray<T> {

 

//Declaring an array, which should store any type T of data

T a[ ];    // Define that the array a[ ] can store any type of data

 

GenericArray(T x) {        // Define a constructor

      a = x;

}

 

T getData(int i) {    // To return the element stored in the i-th place in the array

       return a[i];

}

 

void printData (T b) {  // A generic method to print the elements in array b

     for(int i = 0; i < b.length(); i ++)

               System.out.print(b.getData(i) + “  “); // Print the i-th element in b

                    System.out.println();      // Print a new line

             }

      void reverseArray (T b) { // Generic method to reversed the order of elements in array b

      int front = 0, rear = b.length-1;

      while( front < rear)  {

                   temp = b[rear];

                   b[rear] = a[front];

                   a[front] = temp;

                   front++; rear--;

             }

      }

}

 

Class GenericClassDemo {

     public void static main(String args a[]) {

       //Creating an array of integer data

        Integer x[ ] = {10, 20, 30, 40, 50};      // It is an array of Integer objects

 

       // Store the data into generic array

       GenericArray<Integer> arrayInt = new GenericArray<Integer>(x);

      //Alternatively:

      // GenericArray<Integer> arrayInt = new GenericArray<Integer>(new Integer x[ ] {10, 20, 30, 40, 50});

    // Printing the data….

       printData(arrayInt);          // Printing the array of integer objects

 

     //Reverse ordering of data….

         reverseArray(arrayInt);

 

     // Printing the data after reverse ordering….

       printData(arrayInt);          // Printing the array of integer objects

 

    // -----------------------------------------------------------------------

 

      //Creating an array of String data

        String y[ ] = {“A”, “B”, “C”};      // It is an array of String data

 

       // Store the data into generic array

       GenericArray<String> arrayString = new GenericArray<String>(y);

 

      // Printing the data….

       printData(arrayString);          // Printing the array of strings

 

     //Reverse ordering of data….

         reverseArray(arrayString);

 

     // Printing the data after reverse ordering….

       printData(arrayString);          // Printing the array of strings

 

     // ----------------------------------------------------------------------

 

           //Creating an array of float data

       GenericArray<Double> arrayDouble = new GenericArray<Double>(new Double[]);

     //Creating an array of float data

        Double z[ ] = {1.2, 2.3, 3.4, 4.5};      // It is an array of double data

 

       // Store the data into generic array

       GenericArray<Double> arrayDouble = new GenericArray<Double>(z);

 

      // Printing the data….

       printData(arrayDouble);          // Printing the array of doubles

 

     //Reverse ordering of data….

         reverseArray(arrayDouble);

 

     // Printing the data after reverse ordering….

       printData(arrayDouble);          // Printing the array of doubles

   }

}

 

 

Example 2.12.

Let us start the discussion with a simple generic class declaration and it use. Consider the following program, which define a generic class AnyData. The AnyData class will include a data member  field, whose value can be of any type. Let the generic type be denoted as T. Thus, AnyData <T> is the class with essential member in it.  The class definition of AnyData followed by the driver class using  AnyData<T> class is shown below.

 

public class AnyData <T> {

 

      // Two field of generic type T is defined below

       private T x;

      

    // Constructor

       public AnyData(T t) {

                 x = t;

      }

 

       // Print the T-type value for an object

       public printData() {

           System.out.println (x);

      }

}  // This completes the definition of the generic class AnyData<T>

 

The driver class is programmed below, which creates different types of object of AnyData<T>.

 

class  SimpleGenericClassTest {

       public static void main( String args[] ) { 

                // A data with the member as String

                AnyData<String> a = new AnyData<String> (“Java”);

                a.printData();

 

              // A data with the member as integer value

                AnyData<Integer> b = new AnyData<Integer> (123);

                b.printData();

 

                // A data with the member as float value

                AnyData<Double> c = new AnyData<Double> (3.142);

                c.printData();

      }

}

 

Example 2.13.

This program defines a generic class which declares an array to store regular data such as int, double and String and another array to store another user defined data type, say Student.

 

// Define the user defined Student class

class Student {

      String name;   // Name of the students

      int marks[3];   // Stores the marks in three subjects

           

      // Constructor for the class Student

      Student(String s, int m[ ]) {

           name = s;

           marks = m;

      }

 

      //Defining a method to print student’s record

      void printStudent() {

           System.out.println(“Name : “ + name);     

     System.out.println(“Scores : “ + marks[0] + “  “ + marks[1] + “ 

                                                          + marks[2] );     

        }

 }     // End of the class Student

 

public class AnyData <T> {

 

      // Two field of generic type T is defined below

       private T x;

      

    // Constructor

       public AnyData(T t) {

                 x = t;

      }

 

       // Print the T-type value for an object

       public printData() {

           System.out.println (x);

      }

}  // This completes the definition of the generic class AnyData<T>

 

The driver class is programmed below, which creates different types of object of AnyData<T>.

 

class  SimpleGenericClassTest {

       public static void main( String args[] ) { 

                // A data with the member as String

                AnyData<String> a = new AnyData<String> (“Java”);

                a.printData();

 

              // A data with the member as integer value

                AnyData<Integer> b = new AnyData<Integer> (123);

                b.printData();

 

                // A data with the member as float value

                AnyData<Double> c = new AnyData<Double> (3.142);

                c.printData();

                float marks = {89.9, 91.5, 95.6};

                Student s = new Student(“Priya”,marks);

                 

    // A data with the member as a Student type

                AnyData<Student> d = new AnyData<Student> (s);

                d.printData();

      }

}

 

NOTE:

1.        You cannot instantiate an array whose element type is a type parameter. That is following is invalid.

T a = new T[5];

 

The reason you can’t create an array of T is that there is no way for the compiler to know what type of array to actually create.

 

2.       In parameter type you can not use primitives type like  'int','char' or 'double'. Only class can be referred as the template data. This is why the wrapper class for int, float and string are used as Integer, Double and String, respectively.

 

  1. You can also pass multiple parameters in type specification to design your generic classes.

 

 

 

 

 

 

Generic class with multiple type parameters

We can create a generic class with one or more type parameters so that more than one types of data to be manipulated in a generic program.   Following is the syntax, with an example (see Example 2.14), to define a generic class with two parameters.

 

Example 2.14.

This program illustrates a generic class with multiple parameters.

  

class GC2<T1, T2>  {

    T1 obj1;     // An object of type T1

    T2 obj2;     // An object of type T2

  

    GC2(T1 obj1, T2 obj2)     {   // Constructor

        this.obj1 = obj1;

        this.obj2 = obj2;

    }

}

public void print()      {    // A local method in GC2

        System.out.println(obj1);

        System.out.println(obj2);

    }

}

  

class GC2Test {       // Main class using generic class

    public static void main (String[] args)  {

        GC2 <String, Integer> obj = new GC2<String, Integer>("GC", 9);

        obj.print();

    }

}

 

Example 2.15.

Another example to illustrate generic class with multiple parameters and generic method overloading. It also illustrates how arrays can be handled generically.

 

/* This program illustrates multiple  type parameters in generic class. */

 

// Define the user defined Student class

class Student {

   String name;   // Name of the students

   int marks[3];   // Stores the marks in three subjects

           

   // Constructor for the class Student

   Student(String s, int m[ ]) {

       name = s;

       marks = m;

   }

 

   //Defining a method to print student’s record

    void printStudent() {

        System.out.println(“Name : “ + name);     

  System.out.println(“Scores : “ + marks[0] + “  “ + marks[1] + “ 

                                                          + marks[2] );     

    }

 }     // End of the class Student

 

// Defining a generic array with two type parameters

class GenericArrays<T, S> {

   //Declaring an array, which should store any type T of data

   T a[ ];    // Define that the array a[ ] can store one type of data

   S b[];    // Define that the array b[ ] can store another type of data

   GenericArrays(T x, S y) {        // Define a constructor

 a = x;

       b = y;

   }

 

   T getDataT(int i) {// To return the element stored in i-th place in the array

 return a[i];

   }

 

   S getDataS(int i) { //To return the element stored in i-th place in the array

 return b[i];

   }

 

   void printData (T t) { // A generic method to print the elements in array t

 for(int i = 0; i < t.length(); i ++)

          System.out.print(t.getData(i) + “  “); //Print the i-th element in t

       System.out.println();      // Print a new line

   }

  

   void printData(S s){//An overloaded generic method to print elements in s

 for(int i = 0; i < s.length(); i ++)

          s[i].printStudent()        // Print the i-th student in s

        System.out.println();       // Print a new line

   }

 

void reverseArray(T t) {//Generic method to reverse the order of elements in t

int front = 0, rear = t.length-1; T temp;

while( front < rear)  {

          temp = t[rear];

          t[rear] = t[front];

          t[front] = temp;

          front++; rear--;

      }

 }

 void reverseArray(S s){//Generic method to reverse the order of elements in s

int front = 0, rear = s.length-1; S temp;

while( front < rear)  {

           temp = s[rear]

           s[rear] = s[front];

           s[front] = temp;

           front++; rear--;

      }

   }

}      // End of the definition of class GenericArrays

 

Now, we are in a position to utilize the class GenericArrays in a program. The main program is given below.

 

Class GenericMultiClassesDemo {

     public void static main(String args a[]) {

        //Creating an array of String data

        String t[ ] = {“A”, “B”, “C”};      // It is an array of String data

 

       //Creating an array of Students’ data

        Student s[3];     // It is an array of String data

        s[0] = new Student(“Ram”, 86, 66, 96);

        s[0] = new Student(“Rahim”, 88, 99, 77);

        s[0] = new Student(“John”, 75, 85, 95);

 

       // Store the data into generic arrays

       GenericArrays<String, Student> arrayData = new GenericArrays<String,

                                                             Student>(t, s);

 

       // Printing the data….

       arrayData.printData(t);          // Printing the array of strings

 

       //Reverse ordering of data….

       arrayData.reverseArray(t);

 

       // Printing the data….

       arrayData.printData(s);          // Printing the student’s data

 

       //Reverse ordering of data….

       arrayData.reverseArray(s);

 

       // Printing the data after reverse ordering….

       arrayData.printData(t);          // Printing the array of strings

 

       arrayData.printData(s);          // Printing the array of students

   }

}

 

The following example (Example 2.16), illustrates, in general, the usefulness of generic class and defining a generic class with two parameter types.

 

Example 2.16.

Consider the following program, which define a generic class PairData. The PairData class will include two fields whose values are of any type. Let the generic type be denoted as T and V. Thus, PairData <T, V> is the class with essential members in it.  The class definition of PairData follows the driver class utilizing the PaiaData<T, V> class.

 

public class PairData <T, V> {

      // Two fields of generic type T and V

       private T x;

       private V y;        // Note: How a field can be defined generically.

 

    // Constructor

       public PairData(T a, V b) {

                 x = a;

                 y = b;

      }

 

       // Get the T-type value for a pair-data

       public T getTvalue() {

           return x;

      }

 

       // Get the V-type value for a pair-data

       public V getVvalue() {

           return y;

      }

 

     // To print the data member in an object

      public void printData() {

              System.out.println (getTvalue + “,” getVvalue);

     }

}  // This completes the definition of the class PairData<T, V>

 

The driver class is programmed below.

 

class  GenericClassTest {

    public static void main( String args[] ) { 

        // A pair data with both members as String

        PairData<String, String> a = new PairData<String, String> (“Debasis”,

                                                                  “Samanta”);

        a.printData();

 

        // A pair data with the first member as String and other as Integer

        PairData<String, Integer> b = new PairData<String, Integer> (“Debasis”,

                                                                        789);

        b.printData();

 

        // A pair data with the first member as Integer and other as String

        PairData<Integer, String> c = new PairData<Integer, String> (943,

                                                                  “Samanta”);

        c.printData();

 

        // A pair data with the first member as Integer and other as Double

        PairData<Integer, Double> d = new PairData<Integer, Double> (555,

                                                                    12.34);

        d.printData();

    }

}

 

Bounded types in generic class definition

If a class A is declared as generic with type parameter <T>, then  object of class can be  created of any type. This is fine! But, in several situations, it may cause error during execution. Following is an example to illustrate this.

 

Example 2.17

GenericError<T> {

     T[ ] array;           // An array of type T

     // Pass the constructor a reference to  an array of type T.

     GenericError (T[ ] t) {

          array = t;

     }

 

Text Box: double average() {   // Return type double in all cases
        double sum = 0.0;
        for(int i=0; i < array.length; i++)
          sum += array[i].doubleValue(); // Here is compiler error!
        return sum / array.length;
     }

 

 

 

 

 

 

}

 

Here, you note that the method doubleValue( ) is well defined for all numeric classes, such as Integer, Float and Double, which are sub classes of Number, and Number defines the doubleValue( ) method. Hence, this method is available to all numeric wrapper classes.

Further, you note that you can create object of the class GenericError with another type parameter for which there is no method doubleValue()defined. In other words, the compiler does not have any knowledge about that you are only interested to create objects of numeric types. Thus, the program reports compile-time error showing that the doubleValue() method is unknown.

 

To solve this problem, you need some way to tell the compiler that you intend to pass only numeric types to T. Furthermore, you need some way to ensure that only numeric types are actually passed.

 

To handle such situations, Java provides bounded types. When specifying a type parameter, you can create an upper bound that declares the superclass from which all type arguments must be derived. This is accomplished through the use of an extends clause when specifying the type parameter, as shown here:

 

         <T extends superclass>

 

This specifies that T can only be replaced by superclass, or subclasses of superclass. Thus, superclass defines an inclusive, upper limit.

 

The Generic class definition with bounded class is shown in the following.

 

Example 2.18.

GenericBound<T extends Numbers > {

     T[ ] array;     // an array of type T

  

     // Pass the constructor a reference to  an array of type T.

     GenericBound (T[ ] t) {

           array = t;

     }

 

     double average() {   // Return type double in all cases

          double sum = 0.0;

          for(int i=0; i < array.length; i++)

             sum += array[i].doubleValue();     // Now, it is okay

          return sum / array.length;

     }

}

 

// Driver class is as follows.

 

class GenericBoundDemo {

      public static void main(String args[]) {

      Integer intArray[] = { 1, 2, 3, 4, 5 };

      GenericBound <Integer> intData = new GenericBound

                                                  <Integer>(intArray);

      double avgInt = intData.average();

      System.out.println("Average is " + avgInt);

 

      Double doubleArray[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };

      GenericBound <Double> doubleData = new GenericBound

                                                <Double>(doubleArray);

      double abgDouble = doubleData.average();

      System.out.println("Average is " + avgDouble);

 

      String strArray[] = { "1", "2", "3", "4", "5" };

      GenericBound <String> strData = new GenericBound         

                                                   <String>(strArray);

      /*

      This won't compile because String is not a subclass of Number.                                          

      double avgStr = strData.average();     // ERROR!

      System.out.println("Average is " + avgStr);                          */

 

        }

}

 

Wildcard in Java Generics

The question mark symbol (?) is known as the wildcard in generic programming in Java. Whenever you need to represent an unknown type, you can do that with ?. Java Generic's wildcards is a mechanism to cast a collection of a certain class.  To understand the usefulness of this, let us consider an example first.

 

 

Example 2.19.

The following class definition is to make a program so that a student’s marks can be stored in any number format, that is, Integer, Short, Double, Float, Long, etc.

 

class Student <T extends Numbers> {

     String name;

     T [ ] marks;            // To store the marks obtained by a student

 

     // The usual constructor for the generic class Student

     Student (T [ ] m) {

           marks = m;

     }

 

     // This method to calculate the total of marks by a student

     double total( ) {

          double sum = 0.0;

          for(int i = 0; i < marks.length; i++)

                sum += marks[i].doubleValue();

          return (sum);

     }

 

          // This method compares the marks obtained by this student with another student

 

              boolean compareMarks(Student<T>  otherS) {

         if ( total() == otherS.total() )

              return true;

         return false;

     }

}      // End of the generic class definition

 

This program will compile successfully. Now, let execute the program with different instances of students, whose marks are stored in their own number format. The driver class of using the generic class is given below.

 

/* Driver class while instantiating the Student generic class with different number format.   */

 

class GenericLimitationDemo {

     public static void main(String args[]) {

          // Marks stored in integer for s1

          Integer intMarks1[] = { 44, 55, 33, 66, 77 };                      

          Student<Integer> s1IntMarks= new Student<Integer>(intMarks1);

          System.out.println("Total marks " + s1IntMarks.total());

 

          // Marks stored in integer for s2

     Integer intMarks2[] = { 49, 39, 53, 69 };      

     Student<Integer> s2IntMarks= new Student<Integer>(intMarks2);

     System.out.println("Total marks " + s2IntMarks.total());

 

          // Compare marks between s1 and s2

          if (s1IntMarks.compareMarks (s2IntMarks))

               System.out.println("Same marks");

          else

               System.out.println("Different marks.");

 

          // Marks stored in double for s3     

          Double doubleMarks[] = { 43.5, 55.5, 32.5, 66.5, 77.0 };    

      Student<Double> s3DoubleMarks = new Student<Double>(doubleMarks);

          System.out.println("Total marks " + s3Double.total());

 

          // Marks stored in float for s4     

          Float floatMarks[] = { 50.0F, 40.0F, 60.0F, 65.0F };    

          Student<Float> s4FloatMarks = new Student<Float>(floatMarks);

          System.out.println("Total marks " + s4Float.total());

 

          // Compare marks between s2 and s3

          if(s2IntMarks.compareMarks(s3DoubleMarks)) // Run-time error!

                System.out.println("Same marks");

          else

                System.out.println("Different marks.");

 

      

                       // Compare marks between s3 and s4

         if (s3DoubleMarks. compareMarks (s4FloatMarks))             

                                        // Here, also Run-time error!!

             System.out.println("Same marks");

         else

             System.out.println("Different marks.");

 

     }

}

 

Note:

·         There is no error when s1 is compared with s2; however, the same is not true for s2 and s3 or s3 and s4. The reason is that the si.compareMarks (sj) method works only when the type of the object sj is same as the invoking object  si.

 

Such a problem can be solved by using another feature of Java generics called the wildcard argument. The wildcard argument is specified by the ?, and it just works as the type casting. Thus, with reference to program in Example 2.16, we have to change the boolean compareMarks (Student <T> t) method with wildcard as boolean compareMarks( Student<?> t). See the modified code (Example 2.20).

 

Example 2.20

 class Student <T extends Number> {

     String name;

     T [ ] marks;            // To store the marks obtained by a student

 

     // The usual constructor for the generic class Student

     Student (T [ ] m) {

         marks = m;

     }

 

     // This method to calculate the total of marks by a student

     double total( ) {

          double sum = 0.0;

          for(int i = 0; i < marks.length; i++)

              sum += marks[i].doubleValue();

          return (sum);

     }

 

     /* This method compares the marks obtained by this student with

        another student. */

     boolean compareMarks(Student<?>  otherS) {

          if ( total() == otherS.total() )

                return true;

          return false;

     }

}      // End of the generic class definition

 

The driver code will remain same as in Example 2.16.

 

Note:

·         If we write <? extends Number>, it means any child class of Number, e.g., Integer, Float, and Double. Now we can call the method of Number class through any child class object.

 

Bounded wildcard arguments

We have learnt that how Java’s wildcard feature helps in generic programming. There are other three different ways that wildcard features are useful.

 

1.       Upper bound wildcard

These wildcards can be used when you want to write a method that works on the class where it is defined or any of its sub class.


Syntax:

To declare an upper-bounded wildcard, use the wildcard character (‘?’), followed by the extends keyword, followed by its upper bound class name. For example, say A denotes the upper bound of the class. Then the wildcard uses for the method bounded up to A is

 

                       <type> methodUBA(? extends A) { … }

 

 In other words, the call of this method is valid for any object of the class A or any of its child class.

 

2.       Lower bound wildcard

If you want to limit the call of a method defined in class A and its parent classes only, then you can use lower bound wildcard.

 

Syntax:

It is expressed using the wildcard character (‘?’), followed by the super keyword, followed by its class name.

 

             <type> methodLBA(? super A) { … }

 

3.       Unbounded wildcard

These are useful in the following cases:

·      When writing a method which can be employed using functionality provided in Object class.

·      When the code is using methods in the generic class that don’t depend on the type parameter.

 

<type> methodNBA(?) { … }

 

Let us illustrate the different ways to pass an argument to methods using wildcard in genetic programming. A simple class hierarchy is given (see the figure given below), which will be referred in our incoming discussions.

 

 

 

Figure 2.1: Animal class hierarchy

 

Example 2.21

The objective of this example is to illustrate how different method can be defined with different bounder wildcard arguments. A program is given which consists of the following parts.

 

1.       Definition of all the classes as shown in Fig. 2.1.

2.       Declaration of the generic class, which can be used to store different lists of animals.

3.       Definitions of different methods to handle objects of different classes in the class hierarchy.

4.       Driver class to manipulate the objects of different types.

 

// Defining all the classes as in the animal class hierarchy

 

class Animal {

    long lifespan;

    float weight;

    Animal(long years, float kg) {

        lifespan = years;

        weight = kg;

    }

 

    public void print( ) {

        System.out.println(“Maximum longevity: “ + lifespan + “  in years”);

        System.out.println(“Maximum weight: “   + weight + “  in kgs”);

   }

}   // End of class Animal

 

class Aquatic extends Animal {

      boolean scale;         // true: has scale, false: no scale

      Aquatic(long years, float kg, boolean skin) {

            super(years, kgs);              // Super class constructor

             scale = skin;

        }

 

        public void print( ) {

             super.print();   // Call the super class method

             System.out.println(“Has scale?     + scale);

       }

}   // End of class Aquatic

 

 

class Land extends Animal {

      short vision;   //0 = nocturnal, 1 = only day light,  2 = both

      Land(long years, float kg, short vision) {

           super(years, kgs);              // Super class constructor

           this.vision = vision;

     }

}   // End of class Land

 

class Pet extends Land {

      String name;

      Pet(long years, float kg, short vision, String name) {

          super(years, kgs, vision, name);   // Super class constructor

             this.name = name;

      }

}   // End of class Pet

 

class Wild extends Land {

      float speed;  // Maximum running speed in mph

      Wild(long years, float kg, short vision, float speed) {

          super(years, kgs, vision, name);  // Super class constructor

          this.speed = speed;

     }

}   // End of class Wild

 

                                                   

// Defining the Generic class to maintain lists of different animals

 

class AnimalWorld<T extends Animal> {          

            //Type parameter is limited to Animal and its sub classes

        T [ ] listOfAnimals;

 

        AnimalWorld(T [ ] list)     

            // Generic constructor to create a list of type T

            listOfAnimals = list;

        }

}   // End of the generic class AnimalWorld

 

// Defining different methods with different bounds of arguments

 

class BoundedWildcards {

 

//Case 1: Unbound wildcard: Any object can be passed as its argument.

      

   static void vitality(AnimalWorld<?> animals) {

      //To print the vitality of animals in the list of animals

      for(Animal a : animals)

          a.print();

      System.out.println();

   }

 

// Case 2: Lower bounded wildcard: Any object of Aquatic or Animal can // be passed as its argument.

 

     static void showSea(AnimalWorld<?  super Aquatic> animals) {

          //For aquatic or unknown animals

          for(Object obj : animals)

              obj.print();     

                   // Call the method defined in Animal/ Aquatic class

          System.out.println();

     }

 

// Case 3a: Upper bounded wildcard: Any object of Land/ Pet/ Wild can // be passed as its argument.

 

     static void showLand(AnimalWorld<?  extends Land> animals) {

         //For Land or any of its subclasses

         for(int i = 0; i < animals.listOfAnimals.length)  {

             animals.listOfAnimals[i].print();     

                 // Call the method defined in Animal class

             System.out.println(“Vision : “ +

                                     animals.listOfAnimals[i].vision);

          }

          System.out.println();

     }

 

// Case 3b: Upper bounded wildcard: Any object of only  Pet class can // be passed as its argument.

 

       static void showPet(AnimalWorld<?  extends Pet> animals) {

            //For lists of Pet objects only

            for(int i = 0; i < animals.listOfAnimals.length)  {

                 System.out.println(“Pet’s name: “ +

                                       animals.listOfAnimals[i].name);

                  animals.listOfAnimals[i].print();     

                           // Call the method defined in Animal class

                  System.out.println(“Vision : “ +

                                     animals.listOfAnimals[i].vision);

            }

            System.out.println();

        }

 

// Case 3c: Upper bounded wildcard: Any object of only  Wild class can // be passed as its argument.

 

       static void showWild(AnimalWorld<?  extends Wild> animals) {

            //For objects of Wild class only

            for(int i = 0; i < animals.listOfAnimals.length)  {

                animals.listOfAnimals[i].print();     

                           // Call the method defined in Animal class

                System.out.println(“Maximum running speed: “ +

                          animals.listOfAnimals[i].speed + “ in mph”);

                System.out.println(“Vision : “ +

                          animals.listOfAnimals[i].vision);

            }

            System.out.println();

        }

}  // End of the method definitions in class BoundedWildcards

 

// Main Java program utilizing the above-defined classes

 

class BoundedWildcardArgumentsDemo {

    public static void main(String args[]) {

 

        // Create a list of unknown animals of class Animal

        Animal unknown = new Animal(40, 720);    

                               // An unknown animal object is created

        Animal u [] = {unknown};      // Array of unknown animals

        AnimalWorld<Animal> uList = new AnimalWorld<Animal>(u);    

                                     // Place the unknown into a list

 

        // Create a list of aquatic animals

        Aquatic whale = new Aquatic(90, 150000);    

                                     // A whale object is created

        Aquatic shark = new Aquatic(400, 2150);    

                                     // A shark object is created

        Animal q [] = { whale, shark };     

                                     // Array of aquatic animals

        AnimalWorld<Aquatic> qList = new AnimalWorld<Aquatic>(q);

                                    // Place the aquatics into a list

 

        // Create a list of non-aquatic animals

        Land owl = new Land(3, 1, 0);    

                                      // A land owl object is created

        Land l [] = { owl };    // An array of land objects is created

        AnimalWorld<Land> lList = new AnimalWorld<Land>(l);    

                                         // Place the pets into a list

 

        // Create a list of pet animals

        Pet dog = new Pet(15, 75, 2, “Prince”);    

                                // A pet dog object is created

        Pet p [] = { new Pet(15, 75, 2, “Prince”) };    

                                // An array of pet objects is created

            AnimalWorld<Pet> pList = new AnimalWorld<Pet>(p);    

                                // Place the pets into a list

     

            // Create a list of wild animals

            Wild cheetah = new Land(15, 75, 2);    

                               // A cheetah object is created

            Wild deer = new Land(10, 50, 1);    

                               // A deer object is created            

            Wild w [] = { cheetah, deer };     

                               // Array of non-aquatic animals

            AnimalWorld<Wild> wList = new AnimalWorld<Wild>(w);    

                         // Place the wilds into a list

 

 

            // Call the methods and see the outcomes

            // vitality(…) is with unlimited wildcard argument and

            // hence we can pass argument of any type

            vitality (uList);      // OK

            vitality (qList);     // OK

            vitality (lList);    // OK

            vitality (pList);   // OK

            vitality (wList);  // OK

 

            // showSea(…) is with lower bound wildcard argument with

            // class Aquatic and its super classes

            showSea (uList);      // OK

            showSea (qList);     // OK

            showSea (lList);    // Compile-time error

            showSea (pList);   // Compile-time error

            showSea (wList);  // Compile-time error

 

           // showLand(…) is with upper bound wildcard argument with

           // class Land and its subclasses

            showLand (uList);      // Compile-time error

            showLand (qList);     // Compile-time error

            showLand (lList);    // OK

            showLand (pList);   // OK

            showLand  (wList); // OK

 

            // showPet(…) is with upper bound wildcard argument with

            // class Pet and its subclasses

            showPet (uList);       // Compile-time error

            showPet (qList);      // Compile-time error

            showPet (lList);     // Compile-time error

            showPet (pList);    // OK

            showPet (wList);   // Compile-time error

 

           // showWild(…) is with upper bound wildcard argument with

           // class Wild and its sub classes

            showWild (uList);      // Compile-time error

            showWild (qList);     // Compile-time error

            showWild (lList);    // Compile-time error

            showWild (pList);   // Compile-time error

            showWild (wList);  // OK

      }

}

 

Note:

·         You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both.

 

·         Use extend wildcard when you want to get values out of a structure and super wildcard when you put values in a structure. Don’t use wildcard when you get and put values in a structure.

 

·         Boundedd wildcard argument ensure type safety.

 

·         We can use a wildcard as a type of a parameter, field, return type, or local variable. However, it is not allowed to use a wildcard as a type argument for a generic method invocation, a generic class instance creation, or a supertype.

 

Hint: When to use what?

 

In order to decide which type of wildcard best suits the condition, let's first classify the type of parameters passed to a method as in and out parameter.

 

·         in variable:  An in variable provides data to the code. For example, copy(src, dest). Here src acts as in variable being data to be copied.

 

·         out variable: An out variable holds data updated by the code. For example, copy(src, dest). Here dest acts as in variable having copied data.

 

Guidelines for wildcards.

·         Upper bound wildcard:  If a variable is of in category, use extends keyword with wildcard.

 

·         Lower bound wildcard:  If a variable is of out category, use super keyword with wildcard.

 

·         Unbounded wildcard: If a variable can be accessed using Object class method then use an unbound wildcard.

 

·         No wildcard: If code is accessing variable in both in and out category then do not use wildcards.